package com.lp.user.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lp.feign.ArticleClients;
import com.lp.model.config.AppResult;
import com.lp.model.config.CodeMsg;
import com.lp.model.constants.MQConstants;
import com.lp.model.constants.RedisConstants;
import com.lp.model.dto.user.UserLoginDto;
import com.lp.model.dto.user.UserRegDto;
import com.lp.model.entity.article.ArticleStat;
import com.lp.model.entity.user.User;
import com.lp.model.entity.user.Userinfo;
import com.lp.model.entity.user.vo.UserVO;
import com.lp.model.entity.user.vo.UserinfoVO;
import com.lp.model.exception.ApplicationException;
import com.lp.user.mapper.UserFollowMapper;
import com.lp.user.mapper.UserMapper;
import com.lp.user.mapper.UserinfoMapper;
import com.lp.user.service.IUserService;
import com.lp.model.utils.JwtUtils;
import com.lp.model.utils.PasswordUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class UserService implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserinfoMapper userinfoMapper;

    @Autowired
    private UserFollowMapper userFollowMapper;

    @Autowired
    private ArticleClients articleClients;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @SneakyThrows
    @Override
    public String login(UserLoginDto loginDto) {
        //1.非空校验
        if(loginDto == null || !StringUtils.hasLength(loginDto.getPassword()) ||
                !StringUtils.hasLength(loginDto.getUsername())) {
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_NULL_USER));
        }
        //2.校验密码
        User dbUser = userMapper.selectUserByName(loginDto.getUsername());
        if(dbUser == null) {
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_NOT_EXISTS_USER));
        }
        if(!PasswordUtils.check(loginDto.getPassword(), dbUser.getPassword())) {
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_USER));
        }
        //3.Jwt 生成 token 作为 key，json 格式的用户信息作为值，保存到 redis 上
        HashMap<String, String> map = new HashMap<>();
        map.put("username", loginDto.getUsername());
        String token = JwtUtils.createToken(map);
        String userJson = objectMapper.writeValueAsString(dbUser);
        redisTemplate.opsForValue().set(RedisConstants.TOKEN + token, userJson, 1, TimeUnit.DAYS);
        //4.将 token 数据返回
        log.info("用户上线！{}", dbUser.toString());
        return token;
    }

    @Override
    public void reg(UserRegDto regDto) {
        //1.非空校验
        if(regDto == null ||!StringUtils.hasLength(regDto.getNickname()) ||
                !StringUtils.hasLength(regDto.getUsername()) || !StringUtils.hasLength(regDto.getPassword())) {
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_NULL_USER));
        }
        //2.检查该用户名是否存在
        String username = regDto.getUsername();
        if(userMapper.selectUserByName(username) != null || userinfoMapper.selectUserinfoByUsername(username) != null) {
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_EXISTS_USER));
        }
        log.info("新用户: username=" + regDto.getUsername() + ", password="
                + regDto.getPassword() + ", nickname=" + regDto.getNickname());
        //3.对密码加盐加密
        String finalPassword = PasswordUtils.encrypt(regDto.getPassword());
        regDto.setPassword(finalPassword);
        //4.新增用户数据
        //1) 用户身份数据
        userMapper.add(regDto);
        //2) 用户详细信息数据
        userinfoMapper.add(username);
    }

    @Override
    public User getUserById(Long id) {
        //1.非空校验
        if(id == null) {
            log.info(CodeMsg.FAIL_NULL_USER.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_NULL_USER));
        }
        //2.非法校验
        if(id <= 0) {
            log.info(CodeMsg.FAIL_ERROR_USER.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_USER));
        }
        //3.获取用户信息
        User user = userMapper.selectUserById(id);
        if(user == null) {
            log.info(CodeMsg.FAIL_ERROR_USERID.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_USERID));
        }
        user.setPassword("");
        return user;
    }

    @SneakyThrows
    @Override
    public UserVO getCurUserVO(String token) {
        //1.非空校验
        if(token == null) {
            log.info("token 数据异常！");
            throw new ApplicationException(AppResult.fail("token 数据异常！"));
        }
        //2.从 redis 上获取 token 信息
        String userJson = redisTemplate.opsForValue().get(RedisConstants.TOKEN + token);
        //3.解析 json 格式数据，得到用户信息
        User user = objectMapper.readValue(userJson, User.class);
        //4.计算成就信息，并整合到 VO 中
        return getAchieve(user);
    }

    @SneakyThrows
    @Override
    public void logout(String token) {
        //1.非空校验
        if(token == null) {
            log.info("token 数据异常！");
            throw new ApplicationException(AppResult.fail("token 数据异常！"));
        }
        //2.从 redis 上获取用户信息(用来打印日志)
        String userJson = redisTemplate.opsForValue().get(RedisConstants.TOKEN + token);
        log.info("用户退出登录！{}", userJson);
        //3.从 redis 上删除该用户的 token 数据
        redisTemplate.delete(RedisConstants.TOKEN + token);
    }

    @Override
    public UserinfoVO getUserinfoVOByUsername(String username) {
        //1.获取用户基本信息
        User user = userMapper.selectUserByName(username);
        if(user == null) {
            log.info(CodeMsg.FAIL_ERROR_USERNAME.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_USERNAME));
        }
        //2.获取用户详细信息
        Userinfo userinfo = userinfoMapper.selectUserinfoByUsername(username);
        if(userinfo == null) {
            log.info(CodeMsg.FAIL_ERROR_USERNAME.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_USERNAME));
        }
        //3.整合到 VO 中
        UserinfoVO userinfoVO = getUserinfoVO(user, userinfo);
        return userinfoVO;
    }

    @SneakyThrows
    @Override
    public void update(User user, String token) {
        //1.用户要修改密码
        String password = null;
        if(user.getPassword() != null) {
            password = PasswordUtils.encrypt(password);
        }
        user.setPassword(password);
        //2.修改用户信息
        userMapper.update(user);
        //3.修改 redis 上存储的 token
        //1) 获取刚刚修改的用户信息
        user = userMapper.selectUserById(user.getId());
        if(user == null) {
            log.info(CodeMsg.FAIL_ERROR_USERID.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_USERID));
        }
        //2) 更新 token
        redisTemplate.opsForValue().set(RedisConstants.TOKEN + token,
                objectMapper.writeValueAsString(user), 1, TimeUnit.DAYS);
        //4.mq 通知 es 更改用户信息
        mqSendUpdateUserMsg(user.getId());
    }

    @SneakyThrows
    private void mqSendUpdateUserMsg(Long id) {
        String msg = objectMapper.writeValueAsString(id);
        rabbitTemplate.convertAndSend(MQConstants.ES_EXCHANGE_DIRECT,
                MQConstants.ES_USER_UPDATE_QUEUE, msg);
    }

    @Override
    public void updateUserinfo(Userinfo userinfo) {
        //1.修改用户详细信息
        userinfoMapper.update(userinfo);
        log.info("用户修改了个人信息: {}", userinfo);
    }

    /**
     * 整合 userinfoVO
     */
    private UserinfoVO getUserinfoVO(User user, Userinfo userinfo) {
        UserinfoVO userinfoVO = new UserinfoVO();
        BeanUtils.copyProperties(userinfo, userinfoVO);
        userinfoVO.setNickname(user.getNickname());
        userinfoVO.setBrief(user.getBrief());
        userinfoVO.setPhotoUrl(user.getPhotoUrl());
        return userinfoVO;
    }

    /**
     * 计算用户成绩信息，最后整合到 VO
     * @param user 用户身份信息
     */
    @SneakyThrows
    private UserVO getAchieve(User user) {
        //1.解析 feign 传来的 json 数据(文章统计信息)
        String resultJson = articleClients.userStat(user.getId());
        AppResult result = objectMapper.readValue(resultJson, AppResult.class);
        List<ArticleStat> articleStats = objectMapper.convertValue(result.getData(),
                new TypeReference<List<ArticleStat>>() {});
        //2.计算成就数据
        //1) 总访问量
        long visitsTotal = 0L;
        for(ArticleStat articleStat : articleStats) {
            visitsTotal += articleStat.getVisits();
        }
        //2) 总文章数
        int articleTotal = articleStats.size();
        //3) 总粉丝数
        Integer fansTotal = userFollowMapper.selectFansCountByFollowedId(user.getId());
        //4) 总关注数
        Integer followTotal = userFollowMapper.selectFollowCountByFollowerId(user.getId());
        //3.整合到 VO 中
        UserVO userVO = new UserVO();
        //去敏
        user.setPassword("");
        BeanUtils.copyProperties(user, userVO);
        userVO.setVisitsTotal(visitsTotal);
        userVO.setArticleTotal(articleTotal);
        userVO.setFansTotal(fansTotal);
        userVO.setFollowTotal(followTotal);
        return userVO;
    }

}
